package la.iok.hzsvn.lewin.movie.obs;

import com.obs.services.model.Multipart;
import com.obs.services.model.SpecialParamEnum;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicSessionCredentials;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.model.*;
import com.qcloud.cos.region.Region;
import com.tencentcloudapi.common.provider.STSCredential;
import la.iok.hzsvn.lewin.movie.config.RedisKey;
import la.iok.hzsvn.lewin.movie.config.TencentCosServerProperties;
import la.iok.hzsvn.lewin.movie.core.exception.ErrorCode;
import la.iok.hzsvn.lewin.movie.model.ObsSignedUrl;
import la.iok.hzsvn.lewin.movie.model.SignUrlParam;
import la.iok.hzsvn.redis.service.RedisService;
import la.iok.hzsvn.share.dfs.exception.DfsException;
import la.iok.hzsvn.share.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class TencentObsServiceImpl implements ObsService {
    private static final Logger logger = LoggerFactory.getLogger(TencentObsServiceImpl.class);
    private static final int bufferSize = 1024 * 10;

    private final TencentCosServerProperties tencentCosServerProperties;
    private final RedisService redisService;
    private final RedisKey redisKey;
    
    private final STSCredential readCredential;
    private final STSCredential writeCredential;
    
    public TencentObsServiceImpl(TencentCosServerProperties tencentCosServerProperties,
                                 RedisService redisService, RedisKey redisKey) {
        this.tencentCosServerProperties = tencentCosServerProperties;
        this.redisService = redisService;
        this.redisKey = redisKey;
        String secretId = tencentCosServerProperties.getSecretId();
        String secretKey = tencentCosServerProperties.getSecretKey();
        String readRole = tencentCosServerProperties.getReadRole();
        String writeRole = tencentCosServerProperties.getWriteRole();
        this.readCredential = new STSCredential(secretId, secretKey, readRole, "CosReadTempFromCloud");
        this.writeCredential = new STSCredential(secretId, secretKey, writeRole, "CosWriteTempFromCloud");
    }

    @Override
    public AccessKey getAccessKey(boolean enableWrite) {
        STSCredential credential = enableWrite ? writeCredential : readCredential;
        AccessKey key = new AccessKey();
        key.setAccess(credential.getSecretId());
        key.setSecret(credential.getSecretKey());
        key.setExpiresAt(new Date(new Date().getTime() + 1000 * 60 * 60)); // readRole和writeRole都配置了2小时有效期
        key.setSecurityToken(credential.getToken());
        key.setRegion(tencentCosServerProperties.getRegion());
        key.setBucket(tencentCosServerProperties.getBucketName());
        key.setServerType("tencent");
        return key;
    }

    @Override
    public ObsSignedUrl signUrlPut(String key) {
        if (StringUtils.isNotBlank(key)) {
            AccessKey accessKey = getAccessKey(true);
            return signUrl(accessKey, key, HttpMethod.PUT, null, null);
        }
        return ObsSignedUrl.notExist();
    }

    @Override
    public ObsSignedUrl signUrlPut(String key, Map<String, Object> params) {
        if (StringUtils.isNotBlank(key)) {
            AccessKey accessKey = getAccessKey(true);
            return signUrl(accessKey, key, HttpMethod.PUT, null, params);
        }
        return ObsSignedUrl.notExist();
    }

    @Override
    public ObsSignedUrl signUrlGet(String key) {
        if (StringUtils.isNotBlank(key)) {
            AccessKey accessKey = getAccessKey(false);
            return signUrl(accessKey, key, HttpMethod.GET, null, null);
        }
        return ObsSignedUrl.notExist();
    }

    @Override
    public ObsSignedUrl signUrlPost(String key, SpecialParamEnum specialParamEnum) {
        if (StringUtils.isNotBlank(key)) {
            AccessKey accessKey = getAccessKey(true);
            Map<String, Object> params = new HashMap<>();
            if(specialParamEnum == SpecialParamEnum.UPLOADS) {
                params.put("uploads", "");
            }
            return signUrl(accessKey, key, HttpMethod.POST, null, params);
        }
        return ObsSignedUrl.notExist();
    }

    @Override
    public ObsSignedUrl signUrlPost(String key, Map<String, String> headers, Map<String, Object> params) {
        if (StringUtils.isNotBlank(key)) {
            AccessKey accessKey = getAccessKey(true);
            return signUrl(accessKey, key, HttpMethod.POST, headers, params);
        }
        return ObsSignedUrl.notExist();
    }

    @Override
    public void uploadFile(String storePath, File file) {
        long len = file.length();
        if (len <= 100 * 1024 * 1024) {
            // 100M以内单个文件上传
            uploadNormalFile(storePath, file);
        } else if (len < 5L * 1024 * 1024 * 10000) {
            // 5万M以内,单个分段5M
            uploadLargeFile(storePath, file, 5 * 1024 * 1024);
        } else {
            // 最大分段10000个，计算每个的大小,但是单个分段大小必须在5G以内.按实际情况,每个分段最大1G以内就可以了
            long s = len / 10000;
            if (s >= 1024 * 1024 * 1024) {
                throw new DfsException("文件大小[" + len + "]超过最大值");
            }
            uploadLargeFile(storePath, file, s);
        }
    }

    @Override
    public void downloadFile(String key, OutputStream os) {
        AccessKey accessKey = getAccessKey(false);
        if (accessKey == null || accessKey.expired()) {
            throw new DfsException("获取下载密钥失败");
        }
        
        COSClient cosClient = null;
        try {
            cosClient = createCOSClient(accessKey);
            GetObjectRequest getObjectRequest = new GetObjectRequest(tencentCosServerProperties.getBucketName(), key);
            COSObject cosObject = cosClient.getObject(getObjectRequest);
            COSObjectInputStream cosObjectInput = cosObject.getObjectContent();
            
            byte[] buffer = new byte[bufferSize];
            int len;
            while ((len = cosObjectInput.read(buffer)) > 0) {
                os.write(buffer, 0, len);
            }
            os.flush();
        } catch (IOException e) {
            throw new DfsException("下载文件失败", e);
        } finally {
            if (cosClient != null) {
                cosClient.shutdown();
            }
        }
    }

    @Override
    public void deleteFile(String key) {
        AccessKey accessKey = getAccessKey(true);
        if (accessKey == null || accessKey.expired()) {
            throw new DfsException("获取密钥失败");
        }
        
        COSClient cosClient = null;
        try {
            cosClient = createCOSClient(accessKey);
            if (exist(key)) {
                cosClient.deleteObject(tencentCosServerProperties.getBucketName(), key);
            }
        } catch (Exception e) {
            throw new DfsException("删除文件失败", e);
        } finally {
            if (cosClient != null) {
                cosClient.shutdown();
            }
        }
    }

    @Override
    public boolean exist(String key) {
        AccessKey accessKey = getAccessKey(false);
        if (accessKey == null || accessKey.expired()) {
            throw new DfsException("获取访问密钥失败");
        }
        return exist(key, accessKey);
    }

    @Override
    public long fileLength(String key) {
        AccessKey accessKey = getAccessKey(false);
        if (accessKey == null || accessKey.expired()) {
            throw new DfsException("获取访问密钥失败");
        }
        
        COSClient cosClient = null;
        try {
            cosClient = createCOSClient(accessKey);
            ObjectMetadata objectMetadata = cosClient.getObjectMetadata(tencentCosServerProperties.getBucketName(), key);
            return objectMetadata.getContentLength();
        } catch (CosServiceException e) {
            if (e.getStatusCode() == 404) {
                return 0;
            }
            throw new DfsException("获取文件大小失败", e);
        } finally {
            if (cosClient != null) {
                cosClient.shutdown();
            }
        }
    }

    @Override
    public ObsSignedUrl signUrl(SignUrlParam param) {
        HttpMethod method = HttpMethod.valueOf(StringUtils.isBlank(param.getMethod())?"GET":param.getMethod().toUpperCase());
        AccessKey accessKey;
        String key;
        
        if (method == HttpMethod.POST || method == HttpMethod.PUT || method == HttpMethod.DELETE) {
            accessKey = getAccessKey(true);
            if(StringUtils.isNotBlank(param.getSpecialParam())){
                Map<String, Object> params = param.getParams() != null ? new HashMap<>(param.getParams()) : new HashMap<>();
                params.put(param.getSpecialParam().toLowerCase(), "");
                param.setParams(params);
            }
            // 这里需要配合Redis实现，简化处理
            key = (String) redisService.get(redisKey.signedUrlStorePathKey(param.getUrlId()));
            ErrorCode.PROPERTY_CHECK_ERROR.assertNotBlank(key, "urlId", "查询的记录不存在");
        } else {
            accessKey = getAccessKey(false);
            key = param.getKey();
            ErrorCode.PROPERTY_CHECK_ERROR.assertNotBlank(key, "key", "查询的对象key不能为空");
        }
        
        return signUrl(accessKey, key, method, param.getHeaders(), param.getParams());
    }

    @Override
    public List<Multipart> loadUploadPart(String key, String uploadId) {
        AccessKey accessKey = getAccessKey(false);
        COSClient cosClient = null;
        try {
            cosClient = createCOSClient(accessKey);
            ListPartsRequest request = new ListPartsRequest(tencentCosServerProperties.getBucketName(), key, uploadId);
            PartListing partListing = cosClient.listParts(request);
            // 转换为华为OBS的Multipart格式
            List<Multipart> multipartList = new ArrayList<>();
            for (PartSummary partSummary : partListing.getParts()) {
                Multipart multipart = new Multipart();
                // 设置相关属性，这里根据实际需要进行转换
                multipartList.add(multipart);
            }
            return multipartList;
        } finally {
            if (cosClient != null) {
                cosClient.shutdown();
            }
        }
    }
    
    private boolean exist(String key, AccessKey accessKey) {
        COSClient cosClient = null;
        try {
            cosClient = createCOSClient(accessKey);
            cosClient.getObjectMetadata(tencentCosServerProperties.getBucketName(), key);
            return true;
        } catch (CosServiceException e) {
            if (e.getStatusCode() == 404) {
                return false;
            }
            throw new DfsException("检查文件是否存在失败", e);
        } finally {
            if (cosClient != null) {
                cosClient.shutdown();
            }
        }
    }

    private ObsSignedUrl signUrl(AccessKey accessKey, String key, HttpMethod method, Map<String, String> headers, Map<String, Object> params) {
        COSClient cosClient = null;
        try {
            cosClient = createCOSClient(accessKey);
            
            GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(
                    tencentCosServerProperties.getBucketName(), key, com.qcloud.cos.http.HttpMethodName.valueOf(method.name()));
            
            // 设置过期时间（1小时）
            Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);
            req.setExpiration(expiration);
            
            // 设置请求参数
            if (params != null) {
                for (Map.Entry<String, Object> entry : params.entrySet()) {
                    req.addRequestParameter(entry.getKey(), entry.getValue().toString());
                }
            }
            
            // 设置请求头
            if (headers != null) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    req.putCustomRequestHeader(entry.getKey(), entry.getValue());
                }
            }
            
            URL url = cosClient.generatePresignedUrl(req);
            ObsSignedUrl signedUrl = new ObsSignedUrl();
            signedUrl.setId(UUID.randomUUID().toString());
            signedUrl.setUrl(url.toString());
            // COS不直接返回签名的请求头，这里设为空
            if (headers != null) {
                signedUrl.setHeaders(new HashMap<>(headers));
            }else{
                signedUrl.setHeaders(new HashMap<>());
            }
            if(method == HttpMethod.POST || method == HttpMethod.PUT || method == HttpMethod.DELETE){ //修改操作才写入redis记录
                redisService.set(redisKey.signedUrlStorePathKey(signedUrl.getId()),key,1, TimeUnit.HOURS);  //1小时过期时间
            }
            return signedUrl;
        } finally {
            if (cosClient != null) {
                cosClient.shutdown();
            }
        }
    }

    private void uploadNormalFile(String storePath, File file) {
        AccessKey accessKey = getAccessKey(true);
        if (accessKey == null || accessKey.expired()) {
            throw new DfsException("获取上传密钥失败");
        }
        
        COSClient cosClient = null;
        try {
            cosClient = createCOSClient(accessKey);
            PutObjectRequest putObjectRequest = new PutObjectRequest(tencentCosServerProperties.getBucketName(), storePath, file);
            cosClient.putObject(putObjectRequest);
        } catch (Exception e) {
            throw new DfsException("上传文件失败", e);
        } finally {
            if (cosClient != null) {
                cosClient.shutdown();
            }
        }
    }

    private void uploadLargeFile(String storePath, File file, long segmentSize) {
        AccessKey accessKey = getAccessKey(true);
        if (accessKey == null || accessKey.expired()) {
            throw new DfsException("获取上传密钥失败");
        }
        
        COSClient cosClient = null;
        try {
            cosClient = createCOSClient(accessKey);
            
            // 初始化分块上传
            InitiateMultipartUploadRequest initiateMultipartUploadRequest = 
                    new InitiateMultipartUploadRequest(tencentCosServerProperties.getBucketName(), storePath);
            InitiateMultipartUploadResult initiateMultipartUploadResult = 
                    cosClient.initiateMultipartUpload(initiateMultipartUploadRequest);
            
            String uploadId = initiateMultipartUploadResult.getUploadId();
            if (StringUtils.isBlank(uploadId)) {
                throw new DfsException("初始化分段上传失败");
            }
            
            long len = file.length();
            long parts = len / segmentSize;
            if (len % segmentSize != 0) {
                parts++;
            }
            
            List<PartETag> partETags = new ArrayList<>();
            for (int partNumber = 1; partNumber <= parts; partNumber++) {
                long offset = (partNumber - 1) * segmentSize;
                long size = Math.min(segmentSize, len - offset);
                
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(tencentCosServerProperties.getBucketName());
                uploadPartRequest.setKey(storePath);
                uploadPartRequest.setUploadId(uploadId);
                uploadPartRequest.setPartNumber(partNumber);
                uploadPartRequest.setFile(file);
                uploadPartRequest.setFileOffset(offset);
                uploadPartRequest.setPartSize(size);
                
                UploadPartResult uploadPartResult = cosClient.uploadPart(uploadPartRequest);
                partETags.add(uploadPartResult.getPartETag());
            }
            
            if (partETags.size() == parts) {
                // 完成分块上传
                CompleteMultipartUploadRequest completeMultipartUploadRequest = 
                        new CompleteMultipartUploadRequest(tencentCosServerProperties.getBucketName(), storePath, uploadId, partETags);
                cosClient.completeMultipartUpload(completeMultipartUploadRequest);
            } else {
                logger.error("分段上传文件结果异常：应该有{}段,实际上只有{}段", parts, partETags.size());
                AbortMultipartUploadRequest abortMultipartUploadRequest = 
                        new AbortMultipartUploadRequest(tencentCosServerProperties.getBucketName(), storePath, uploadId);
                cosClient.abortMultipartUpload(abortMultipartUploadRequest);
                throw new DfsException("分段上传文件" + file.getName() + "失败");
            }
        } catch (Exception e) {
            throw new DfsException("分段上传文件" + file.getName() + "失败", e);
        } finally {
            if (cosClient != null) {
                cosClient.shutdown();
            }
        }
    }

    private COSClient createCOSClient(AccessKey accessKey) {
        String tmpSecretId = accessKey.getAccess();
        String tmpSecretKey = accessKey.getSecret();
        String sessionToken = accessKey.getSecurityToken();
        
        BasicSessionCredentials cred = new BasicSessionCredentials(tmpSecretId, tmpSecretKey, sessionToken);
        Region region = new Region(tencentCosServerProperties.getRegion());
        ClientConfig clientConfig = new ClientConfig(region);
        return new COSClient(cred, clientConfig);
    }
}
